🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / messaging / src / commonMain / kotlin / org / meshtastic / feature / messaging / MessageListPaged.kt
Displaying Raw • Download
feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageListPaged.kt bc5cf83df7fd6b405ffc521d7c3ed4e1f3e8271a (bc5cf83d) Text, 22.03 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.messaging
Tff7b72import T7ee787androidx.compose.foundation.layout.Box
Tff7b72import T7ee787androidx.compose.foundation.layout.Column
Tff7b72import T7ee787androidx.compose.foundation.layout.PaddingValues
Tff7b72import T7ee787androidx.compose.foundation.layout.fillMaxSize
Tff7b72import T7ee787androidx.compose.foundation.layout.fillMaxWidth
Tff7b72import T7ee787androidx.compose.foundation.layout.padding
Tff7b72import T7ee787androidx.compose.foundation.lazy.LazyColumn
Tff7b72import T7ee787androidx.compose.foundation.lazy.LazyListState
Tff7b72import T7ee787androidx.compose.foundation.lazy.rememberLazyListState
Tff7b72import T7ee787androidx.compose.material3.CircularProgressIndicator
Tff7b72import T7ee787androidx.compose.runtime.Composable
Tff7b72import T7ee787androidx.compose.runtime.LaunchedEffect
Tff7b72import T7ee787androidx.compose.runtime.MutableState
Tff7b72import T7ee787androidx.compose.runtime.derivedStateOf
Tff7b72import T7ee787androidx.compose.runtime.getValue
Tff7b72import T7ee787androidx.compose.runtime.mutableStateOf
Tff7b72import T7ee787androidx.compose.runtime.remember
Tff7b72import T7ee787androidx.compose.runtime.rememberCoroutineScope
Tff7b72import T7ee787androidx.compose.runtime.rememberUpdatedState
Tff7b72import T7ee787androidx.compose.runtime.setValue
Tff7b72import T7ee787androidx.compose.runtime.snapshotFlow
Tff7b72import T7ee787androidx.compose.ui.Alignment
Tff7b72import T7ee787androidx.compose.ui.Modifier
Tff7b72import T7ee787androidx.compose.ui.hapticfeedback.HapticFeedback
Tff7b72import T7ee787androidx.compose.ui.hapticfeedback.HapticFeedbackType
Tff7b72import T7ee787androidx.compose.ui.platform.LocalHapticFeedback
Tff7b72import T7ee787androidx.compose.ui.unit.dp
Tff7b72import T7ee787androidx.lifecycle.compose.LifecycleResumeEffect
Tff7b72import T7ee787androidx.paging.LoadState
Tff7b72import T7ee787androidx.paging.compose.LazyPagingItems
Tff7b72import T7ee787androidx.paging.compose.itemContentType
Tff7b72import T7ee787androidx.paging.compose.itemKey
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.FlowPreview
Tff7b72import T7ee787kotlinx.coroutines.flow.collectLatest
Tff7b72import T7ee787kotlinx.coroutines.flow.debounce
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787org.meshtastic.core.model.ContactKey
Tff7b72import T7ee787org.meshtastic.core.model.Message
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.model.Reaction
Tff7b72import T7ee787org.meshtastic.feature.messaging.component.MessageItem
Tff7b72import T7ee787org.meshtastic.feature.messaging.component.MessageStatusDialog
Tff7b72import T7ee787org.meshtastic.feature.messaging.component.ReactionDialog
Tff7b72import T7ee787org.meshtastic.feature.messaging.component.UnreadMessagesDivider
Tff7b72import T7ee787kotlin.math.abs
Tff7b72private Tff7b72const Tff7b72val Te6edf3HEX_RADIX Tff7b72= T79c0ff1T79c0ff6
T8b949e/**
* Messages merge into one visual group only when they are from the same sender AND close in time. The group header
* carries the run's only timestamp, so a run must never span messages minutes apart from what the header shows.
*/
Tff7b72internal Tff7b72const Tff7b72val Te6edf3GROUPING_WINDOW_MILLIS Tff7b72= T79c0ff1T79c0ff0 Tff7b72* T79c0ff6T79c0ff0 Tff7b72* T79c0ff1T79c0ff0T79c0ff0T79c0ff0L
Tff7b72internal Tff7b72fun Td2a8ffisSameGroupTb4b4b4(Te6edf3olderTb4b4b4: Te6edf3MessageTb4b4b4, Te6edf3newerTb4b4b4: Te6edf3MessageTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3olderTb4b4b4.Te6edf3fromLocal Tff7b72=Tff7b72= Te6edf3newerTb4b4b4.Te6edf3fromLocal Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3newerTb4b4b4.Te6edf3fromLocal Tff7b72|Tff7b72| Te6edf3olderTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3num Tff7b72=Tff7b72= Te6edf3newerTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3numTb4b4b4) Tff7b72&Tff7b72&
Te6edf3absTb4b4b4(Te6edf3newerTb4b4b4.Te6edf3receivedTime Tff7b72- Te6edf3olderTb4b4b4.Te6edf3receivedTimeTb4b4b4) Tff7b72<Tff7b72= Te6edf3GROUPING_WINDOW_MILLIS
Tff7b72internal Tff7b72data Tff7b72class T56d364MessageListHandlersTb4b4b4(
Tff7b72val Te6edf3onUnreadChangedTb4b4b4: Tb4b4b4(Tffa657LongTb4b4b4, Tffa657LongTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onSendReactionTb4b4b4: Tb4b4b4(Tffa657StringTb4b4b4, Tffa657IntTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onClickChipTb4b4b4: Tb4b4b4(Te6edf3NodeTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onDeleteMessagesTb4b4b4: Tb4b4b4(Te6edf3ListTff7b72<Tffa657LongTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onSendMessageTb4b4b4: Tb4b4b4(Tffa657StringTb4b4b4, Tffa657StringTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onReplyTb4b4b4: Tb4b4b4(Te6edf3Message?Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tff7b72val Te6edf3onTranslateTb4b4b4: Tb4b4b4(Te6edf3MessageTb4b4b4) Tff7b72-Tff7b72> Tffa657Unit Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tff7b72val Te6edf3onToggleTranslationTb4b4b4: Tb4b4b4(Te6edf3MessageTb4b4b4) Tff7b72-Tff7b72> Tffa657Unit Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72internal Tff7b72data Tff7b72class T56d364MessageListPagedStateTb4b4b4(
Tff7b72val Te6edf3nodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeTff7b72>Tb4b4b4,
Tff7b72val Te6edf3ourNodeTb4b4b4: Te6edf3Node?Tb4b4b4,
Tff7b72val Te6edf3messagesTb4b4b4: Te6edf3LazyPagingItemsTff7b72<Te6edf3MessageTff7b72>Tb4b4b4,
Tff7b72val Te6edf3selectedIdsTb4b4b4: Te6edf3MutableStateTff7b72<Te6edf3SetTff7b72<Tffa657LongTff7b72>Tff7b72>Tb4b4b4,
Tff7b72val Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3firstUnreadMessageUuidTb4b4b4: Tffa657Long? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72val Te6edf3hasUnreadMessagesTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tff7b72val Te6edf3filteredCountTb4b4b4: Tffa657Int Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72val Te6edf3showFilteredTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tff7b72val Te6edf3filteringDisabledTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tff7b72val Te6edf3searchQueryTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4,
Tff7b72val Te6edf3translationAvailableTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffMutableStateTff7b72<Te6edf3SetTff7b72<Tffa657LongTff7b72>Tff7b72>Tb4b4b4.Te6edf3toggleTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4) Tb4b4b4{
Te6edf3value Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3valueTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3uuidTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3value Tff7b72- Te6edf3uuid
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3value Tff7b72+ Te6edf3uuid
Tb4b4b4}
Tb4b4b4}
Tf0883e@Composable
Tff7b72internal Tff7b72fun Td2a8ffMessageListPagedTb4b4b4(
Te6edf3stateTb4b4b4: Te6edf3MessageListPagedStateTb4b4b4,
Te6edf3handlersTb4b4b4: Te6edf3MessageListHandlersTb4b4b4,
Te6edf3modifierTb4b4b4: Te6edf3Modifier Tff7b72= Te6edf3ModifierTb4b4b4,
Te6edf3listStateTb4b4b4: Te6edf3LazyListState Tff7b72= Te6edf3rememberLazyListStateTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3quickEmojisTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72> Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3haptics Tff7b72= Te6edf3LocalHapticFeedbackTb4b4b4.Te6edf3current
Tff7b72val Te6edf3inSelectionMode Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3derivedStateOf Tb4b4b4{ Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tb4b4b4} Tb4b4b4}
T8b949e// Optimization: Pre-calculate map for O(1) lookup in list items to avoid O(N) linear search during scrolling.
Tff7b72val Te6edf3nodeMap Tff7b72= Te6edf3rememberTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3nodesTb4b4b4) Tb4b4b4{ Te6edf3stateTb4b4b4.Te6edf3nodesTb4b4b4.Te6edf3associateBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4} Tb4b4b4}
Tff7b72val Te6edf3isDirectMessageConversation Tff7b72=
Te6edf3rememberTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3contactKeyTb4b4b4) Tb4b4b4{ Te6edf3ContactKeyTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3contactKeyTb4b4b4)Tb4b4b4.Te6edf3addressString Tff7b72!Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCAST Tb4b4b4}
Tff7b72var Te6edf3showStatusDialog Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3mutableStateOfTff7b72<Te6edf3Message?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4) Tb4b4b4}
Te6edf3showStatusDialogTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3message Tff7b72-Tff7b72>
Te6edf3MessageStatusDialogTb4b4b4(
Te6edf3message Tff7b72= Te6edf3messageTb4b4b4,
Te6edf3isDirectMessage Tff7b72= Te6edf3isDirectMessageConversationTb4b4b4,
Te6edf3resendOption Tff7b72= Te6edf3messageTb4b4b4.Te6edf3isStatusRetryableTb4b4b4(Te6edf3isDirectMessageConversationTb4b4b4)Tb4b4b4,
Te6edf3onResend Tff7b72= Tb4b4b4{
Te6edf3handlersTb4b4b4.Te6edf3onDeleteMessagesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4)Tb4b4b4)
Te6edf3handlersTb4b4b4.Te6edf3onSendMessageTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3textTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3contactKeyTb4b4b4)
Te6edf3showStatusDialog Tff7b72= Tff7b72null
Tb4b4b4}Tb4b4b4,
Te6edf3onDismiss Tff7b72= Tb4b4b4{ Te6edf3showStatusDialog Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72var Te6edf3showReactionDialog Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3mutableStateOfTff7b72<Te6edf3ListTff7b72<Te6edf3ReactionTff7b72>Tff7b72?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4) Tb4b4b4}
Te6edf3showReactionDialogTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3reactions Tff7b72-Tff7b72>
Te6edf3ReactionDialogTb4b4b4(
Te6edf3reactions Tff7b72= Te6edf3reactionsTb4b4b4,
Te6edf3myId Tff7b72= Te6edf3stateTb4b4b4.Te6edf3ourNodeTff7b72?.Te6edf3userTff7b72?.Te6edf3idTb4b4b4,
Te6edf3onDismiss Tff7b72= Tb4b4b4{ Te6edf3showReactionDialog Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4,
Te6edf3onResend Tff7b72= Tb4b4b4{ Te6edf3reaction Tff7b72-Tff7b72>
Te6edf3handlersTb4b4b4.Te6edf3onSendReactionTb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3emojiTb4b4b4, Te6edf3reactionTb4b4b4.Te6edf3replyIdTb4b4b4)
Te6edf3showReactionDialog Tff7b72= Tff7b72null
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3coroutineScope Tff7b72= Te6edf3rememberCoroutineScopeTb4b4b4(Tb4b4b4)
T8b949e// Disable auto-scroll when any dialog is open to prevent list jumping
Tff7b72val Te6edf3hasDialogOpen Tff7b72= Te6edf3showStatusDialog Tff7b72!Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3showReactionDialog Tff7b72!Tff7b72= Tff7b72null
T8b949e// Track unread count based on scroll position
Te6edf3UpdateUnreadCountPagedTb4b4b4(Te6edf3listState Tff7b72= Te6edf3listStateTb4b4b4, Te6edf3messages Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4, Te6edf3onUnreadChange Tff7b72= Te6edf3handlersTb4b4b4.Te6edf3onUnreadChangedTb4b4b4)
T8b949e// Auto-scroll to bottom when new messages arrive
Te6edf3AutoScrollToBottomPagedTb4b4b4(
Te6edf3listState Tff7b72= Te6edf3listStateTb4b4b4,
Te6edf3messages Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4,
Te6edf3hasUnreadMessages Tff7b72= Te6edf3stateTb4b4b4.Te6edf3hasUnreadMessagesTb4b4b4,
Te6edf3hasDialogOpen Tff7b72= Te6edf3hasDialogOpenTb4b4b4,
Tb4b4b4)
Te6edf3MessageListPagedContentTb4b4b4(
Te6edf3listState Tff7b72= Te6edf3listStateTb4b4b4,
Te6edf3state Tff7b72= Te6edf3stateTb4b4b4,
Te6edf3nodeMap Tff7b72= Te6edf3nodeMapTb4b4b4,
Te6edf3handlers Tff7b72= Te6edf3handlersTb4b4b4,
Te6edf3inSelectionMode Tff7b72= Te6edf3inSelectionModeTb4b4b4,
Te6edf3coroutineScope Tff7b72= Te6edf3coroutineScopeTb4b4b4,
Te6edf3haptics Tff7b72= Te6edf3hapticsTb4b4b4,
Te6edf3isDirectMessageConversation Tff7b72= Te6edf3isDirectMessageConversationTb4b4b4,
Te6edf3onShowStatusDialog Tff7b72= Tb4b4b4{ Te6edf3showStatusDialog Tff7b72= Tffa657it Tb4b4b4}Tb4b4b4,
Te6edf3onShowReactions Tff7b72= Tb4b4b4{ Te6edf3showReactionDialog Tff7b72= Tffa657it Tb4b4b4}Tb4b4b4,
Te6edf3modifier Tff7b72= Te6edf3modifierTb4b4b4,
Te6edf3quickEmojis Tff7b72= Te6edf3quickEmojisTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4)
Tf0883e@Composable
Tff7b72private Tff7b72fun Td2a8ffMessageListPagedContentTb4b4b4(
Te6edf3listStateTb4b4b4: Te6edf3LazyListStateTb4b4b4,
Te6edf3stateTb4b4b4: Te6edf3MessageListPagedStateTb4b4b4,
Te6edf3nodeMapTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3handlersTb4b4b4: Te6edf3MessageListHandlersTb4b4b4,
Te6edf3inSelectionModeTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3coroutineScopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Te6edf3hapticsTb4b4b4: Te6edf3HapticFeedbackTb4b4b4,
Te6edf3isDirectMessageConversationTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3onShowStatusDialogTb4b4b4: Tb4b4b4(Te6edf3MessageTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Te6edf3onShowReactionsTb4b4b4: Tb4b4b4(Te6edf3ListTff7b72<Te6edf3ReactionTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Te6edf3modifierTb4b4b4: Te6edf3Modifier Tff7b72= Te6edf3ModifierTb4b4b4,
Te6edf3quickEmojisTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4,
Tb4b4b4) Tb4b4b4{
T8b949e// Calculate unread divider position using snapshot to avoid side-effects and improve performance
T8b949e// Optimized: Use full snapshot index to correctly match LazyColumn index range
Tff7b72val Te6edf3unreadDividerIndex Tff7b72by
Te6edf3rememberTb4b4b4(Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemCountTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3firstUnreadMessageUuidTb4b4b4) Tb4b4b4{
Te6edf3derivedStateOf Tb4b4b4{
Tff7b72val Te6edf3uuid Tff7b72= Te6edf3stateTb4b4b4.Te6edf3firstUnreadMessageUuid Tff7b72?: Tff7b72returnTf0883e@derivedStateOf Tff7b72null
Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemSnapshotListTb4b4b4.Te6edf3indexOfFirst Tb4b4b4{ Tffa657itTff7b72?.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3uuid Tb4b4b4}Tb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72!Tff7b72= Tff7b72-T79c0ff1 Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Disable animations during scroll to prevent jank/stutter
Tff7b72val Te6edf3enableAnimations Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3derivedStateOf Tb4b4b4{ Tff7b72!Te6edf3listStateTb4b4b4.Te6edf3isScrollInProgress Tb4b4b4} Tb4b4b4}
Te6edf3BoxTb4b4b4(Te6edf3modifier Tff7b72= Te6edf3modifierTb4b4b4.Te6edf3fillMaxSizeTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LazyColumnTb4b4b4(
Te6edf3modifier Tff7b72= Te6edf3ModifierTb4b4b4.Te6edf3fillMaxSizeTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3state Tff7b72= Te6edf3listStateTb4b4b4,
Te6edf3reverseLayout Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3contentPadding Tff7b72= Te6edf3PaddingValuesTb4b4b4(Te6edf3bottom Tff7b72= T79c0ff2T79c0ff4.Te6edf3dpTb4b4b4)Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3itemsTb4b4b4(
Te6edf3count Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemCountTb4b4b4,
Te6edf3key Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemKey Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3uuid Tb4b4b4}Tb4b4b4,
Te6edf3contentType Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemContentType Tb4b4b4{ Ta5d6ff"Ta5d6ffmessageTa5d6ff" Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72>
Tff7b72val Te6edf3message Tff7b72= Te6edf3stateTb4b4b4.Te6edf3messagesTff7b72[Te6edf3indexTff7b72]
Tff7b72val Te6edf3visuallyPrevMessage Tff7b72= Tff7b72if Tb4b4b4(Te6edf3index Tff7b72< Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemCount Tff7b72- T79c0ff1Tb4b4b4) Te6edf3stateTb4b4b4.Te6edf3messagesTff7b72[Te6edf3index Tff7b72+ T79c0ff1Tff7b72] Tff7b72else Tff7b72null
Tff7b72val Te6edf3visuallyNextMessage Tff7b72= Tff7b72if Tb4b4b4(Te6edf3index Tff7b72> T79c0ff0Tb4b4b4) Te6edf3stateTb4b4b4.Te6edf3messagesTff7b72[Te6edf3index Tff7b72- T79c0ff1Tff7b72] Tff7b72else Tff7b72null
Tff7b72val Te6edf3hasSamePrev Tff7b72=
Te6edf3message Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3visuallyPrevMessage Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3isSameGroupTb4b4b4(Te6edf3visuallyPrevMessageTb4b4b4, Te6edf3messageTb4b4b4)
Tff7b72val Te6edf3hasSameNext Tff7b72=
Te6edf3message Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3visuallyNextMessage Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3isSameGroupTb4b4b4(Te6edf3messageTb4b4b4, Te6edf3visuallyNextMessageTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3message Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3isFirstUnread Tff7b72= Te6edf3stateTb4b4b4.Te6edf3hasUnreadMessages Tff7b72&Tff7b72& Te6edf3unreadDividerIndex Tff7b72=Tff7b72= Te6edf3index
Tff7b72val Te6edf3itemModifier Tff7b72= Tff7b72if Tb4b4b4(Te6edf3enableAnimationsTb4b4b4) Te6edf3ModifierTb4b4b4.Te6edf3animateItemTb4b4b4(Tb4b4b4) Tff7b72else Te6edf3Modifier
Tff7b72if Tb4b4b4(Te6edf3isFirstUnreadTb4b4b4) Tb4b4b4{
T8b949e// Wrap in Column to prevent overlapping of divider and message item
T8b949e// Apply animation to the container Column once
Te6edf3ColumnTb4b4b4(Te6edf3modifier Tff7b72= Te6edf3itemModifierTb4b4b4) Tb4b4b4{
Te6edf3UnreadMessagesDividerTb4b4b4(Tb4b4b4)
Te6edf3RenderPagedChatMessageRowTb4b4b4(
Te6edf3message Tff7b72= Te6edf3messageTb4b4b4,
Te6edf3state Tff7b72= Te6edf3stateTb4b4b4,
Te6edf3nodeMap Tff7b72= Te6edf3nodeMapTb4b4b4,
Te6edf3handlers Tff7b72= Te6edf3handlersTb4b4b4,
Te6edf3inSelectionMode Tff7b72= Te6edf3inSelectionModeTb4b4b4,
Te6edf3coroutineScope Tff7b72= Te6edf3coroutineScopeTb4b4b4,
Te6edf3haptics Tff7b72= Te6edf3hapticsTb4b4b4,
Te6edf3isDirectMessageConversation Tff7b72= Te6edf3isDirectMessageConversationTb4b4b4,
Te6edf3listState Tff7b72= Te6edf3listStateTb4b4b4,
Te6edf3onShowStatusDialog Tff7b72= Te6edf3onShowStatusDialogTb4b4b4,
Te6edf3onShowReactions Tff7b72= Te6edf3onShowReactionsTb4b4b4,
Te6edf3showUserName Tff7b72= Tff7b72!Te6edf3hasSamePrevTb4b4b4,
Te6edf3hasSamePrev Tff7b72= Te6edf3hasSamePrevTb4b4b4,
Te6edf3hasSameNext Tff7b72= Te6edf3hasSameNextTb4b4b4,
Te6edf3quickEmojis Tff7b72= Te6edf3quickEmojisTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3RenderPagedChatMessageRowTb4b4b4(
Te6edf3message Tff7b72= Te6edf3messageTb4b4b4,
Te6edf3state Tff7b72= Te6edf3stateTb4b4b4,
Te6edf3nodeMap Tff7b72= Te6edf3nodeMapTb4b4b4,
Te6edf3handlers Tff7b72= Te6edf3handlersTb4b4b4,
Te6edf3inSelectionMode Tff7b72= Te6edf3inSelectionModeTb4b4b4,
Te6edf3coroutineScope Tff7b72= Te6edf3coroutineScopeTb4b4b4,
Te6edf3haptics Tff7b72= Te6edf3hapticsTb4b4b4,
Te6edf3isDirectMessageConversation Tff7b72= Te6edf3isDirectMessageConversationTb4b4b4,
Te6edf3listState Tff7b72= Te6edf3listStateTb4b4b4,
Te6edf3onShowStatusDialog Tff7b72= Te6edf3onShowStatusDialogTb4b4b4,
Te6edf3onShowReactions Tff7b72= Te6edf3onShowReactionsTb4b4b4,
Te6edf3modifier Tff7b72= Te6edf3itemModifierTb4b4b4,
Te6edf3showUserName Tff7b72= Tff7b72!Te6edf3hasSamePrevTb4b4b4,
Te6edf3hasSamePrev Tff7b72= Te6edf3hasSamePrevTb4b4b4,
Te6edf3hasSameNext Tff7b72= Te6edf3hasSameNextTb4b4b4,
Te6edf3quickEmojis Tff7b72= Te6edf3quickEmojisTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Loading indicator at the end (top when reversed) when loading more items
Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3apply Tb4b4b4{
Tff7b72when Tb4b4b4{
Te6edf3loadStateTb4b4b4.Te6edf3append Tff7b72is Te6edf3LoadStateTb4b4b4.Te6edf3Loading Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3itemTb4b4b4(Te6edf3key Tff7b72= Ta5d6ff"Ta5d6ffappend_loadingTa5d6ff"Tb4b4b4, Te6edf3contentType Tff7b72= Ta5d6ff"Ta5d6ffloadingTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3BoxTb4b4b4(
Te6edf3modifier Tff7b72= Te6edf3ModifierTb4b4b4.Te6edf3fillMaxWidthTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3paddingTb4b4b4(T79c0ff1T79c0ff6.Te6edf3dpTb4b4b4)Tb4b4b4,
Te6edf3contentAlignment Tff7b72= Te6edf3AlignmentTb4b4b4.Te6edf3CenterTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3CircularProgressIndicatorTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongMethodTa5d6ff"Tb4b4b4)
Tf0883e@Composable
Tff7b72private Tff7b72fun Td2a8ffRenderPagedChatMessageRowTb4b4b4(
Te6edf3messageTb4b4b4: Te6edf3MessageTb4b4b4,
Te6edf3stateTb4b4b4: Te6edf3MessageListPagedStateTb4b4b4,
Te6edf3nodeMapTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3handlersTb4b4b4: Te6edf3MessageListHandlersTb4b4b4,
Te6edf3inSelectionModeTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3coroutineScopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4,
Te6edf3hapticsTb4b4b4: Te6edf3HapticFeedbackTb4b4b4,
Te6edf3isDirectMessageConversationTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3listStateTb4b4b4: Te6edf3LazyListStateTb4b4b4,
Te6edf3onShowStatusDialogTb4b4b4: Tb4b4b4(Te6edf3MessageTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Te6edf3onShowReactionsTb4b4b4: Tb4b4b4(Te6edf3ListTff7b72<Te6edf3ReactionTff7b72>Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Te6edf3modifierTb4b4b4: Te6edf3Modifier Tff7b72= Te6edf3ModifierTb4b4b4,
Te6edf3showUserNameTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hasSamePrevTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hasSameNextTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3quickEmojisTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3ourNode Tff7b72= Te6edf3stateTb4b4b4.Te6edf3ourNode Tff7b72?: Tff7b72return
Tff7b72val Te6edf3selected Tff7b72by
Te6edf3rememberTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3valueTb4b4b4) Tb4b4b4{
Te6edf3derivedStateOf Tb4b4b4{ Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72val Te6edf3node Tff7b72= Te6edf3nodeMapTff7b72[Te6edf3messageTb4b4b4.Te6edf3nodeTb4b4b4.Te6edf3numTff7b72] Tff7b72?: Te6edf3messageTb4b4b4.Te6edf3node
T8b949e// Resolve an @mention token ("!<hex>" = numeric node id) back to its node for live name + tap-to-open.
Tff7b72val Te6edf3resolveMentionTb4b4b4: Tb4b4b4(Tffa657StringTb4b4b4) Tff7b72-Tff7b72> Te6edf3Node? Tff7b72=
Te6edf3rememberTb4b4b4(Te6edf3nodeMapTb4b4b4) Tb4b4b4{ Tb4b4b4{ Te6edf3id Tff7b72-Tff7b72> Te6edf3idTb4b4b4.Te6edf3removePrefixTb4b4b4(Ta5d6ff"Ta5d6ff!Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3toLongOrNullTb4b4b4(Te6edf3HEX_RADIXTb4b4b4)Tff7b72?.Te6edf3toIntTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3nodeMapTff7b72[Tffa657itTff7b72] Tb4b4b4} Tb4b4b4} Tb4b4b4}
Te6edf3MessageItemTb4b4b4(
Te6edf3modifier Tff7b72= Te6edf3modifierTb4b4b4,
Te6edf3node Tff7b72= Te6edf3nodeTb4b4b4,
Te6edf3ourNode Tff7b72= Te6edf3ourNodeTb4b4b4,
Te6edf3message Tff7b72= Te6edf3messageTb4b4b4,
Te6edf3selected Tff7b72= Te6edf3selectedTb4b4b4,
Te6edf3inSelectionMode Tff7b72= Te6edf3inSelectionModeTb4b4b4,
Te6edf3onClick Tff7b72= Tb4b4b4{ Tff7b72if Tb4b4b4(Te6edf3inSelectionModeTb4b4b4) Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3toggleTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onLongClick Tff7b72= Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3inSelectionModeTb4b4b4) Tb4b4b4{
Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3toggleTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4)
Tb4b4b4}
Te6edf3hapticsTb4b4b4.Te6edf3performHapticFeedbackTb4b4b4(Te6edf3HapticFeedbackTypeTb4b4b4.Te6edf3LongPressTb4b4b4)
Tb4b4b4}Tb4b4b4,
Te6edf3onSelect Tff7b72= Tb4b4b4{ Te6edf3stateTb4b4b4.Te6edf3selectedIdsTb4b4b4.Te6edf3toggleTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onDelete Tff7b72= Tb4b4b4{ Te6edf3handlersTb4b4b4.Te6edf3onDeleteMessagesTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3uuidTb4b4b4)Tb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onClickChip Tff7b72= Te6edf3handlersTb4b4b4.Te6edf3onClickChipTb4b4b4,
Te6edf3resolveMention Tff7b72= Te6edf3resolveMentionTb4b4b4,
Te6edf3onStatusClick Tff7b72= Tb4b4b4{ Te6edf3onShowStatusDialogTb4b4b4(Te6edf3messageTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onReply Tff7b72= Tb4b4b4{ Te6edf3handlersTb4b4b4.Te6edf3onReplyTb4b4b4(Te6edf3messageTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3emojis Tff7b72= Te6edf3messageTb4b4b4.Te6edf3emojisTb4b4b4,
Te6edf3showUserName Tff7b72= Te6edf3showUserNameTb4b4b4,
Te6edf3sendReaction Tff7b72= Tb4b4b4{ Te6edf3emoji Tff7b72-Tff7b72>
Tff7b72val Te6edf3hasReacted Tff7b72=
Te6edf3messageTb4b4b4.Te6edf3emojisTb4b4b4.Te6edf3any Tb4b4b4{ Te6edf3reaction Tff7b72-Tff7b72>
Tb4b4b4(
Te6edf3reactionTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3ourNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72|Tff7b72|
Te6edf3reactionTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3NodeAddressTb4b4b4.Te6edf3ID_LOCAL
Tb4b4b4) Tff7b72&Tff7b72& Te6edf3reactionTb4b4b4.Te6edf3emoji Tff7b72=Tff7b72= Te6edf3emoji
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3hasReactedTb4b4b4) Tb4b4b4{
Te6edf3handlersTb4b4b4.Te6edf3onSendReactionTb4b4b4(Te6edf3emojiTb4b4b4, Te6edf3messageTb4b4b4.Te6edf3packetIdTb4b4b4)
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3onShowReactions Tff7b72= Tb4b4b4{ Te6edf3onShowReactionsTb4b4b4(Te6edf3messageTb4b4b4.Te6edf3emojisTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onNavigateToOriginalMessage Tff7b72= Tb4b4b4{
Te6edf3coroutineScopeTb4b4b4.Te6edf3launch Tb4b4b4{
T8b949e// Note: With pagination, we can't guarantee the original message is loaded
T8b949e// Optimized: Use snapshot to find index to avoid side-effects during search
Tff7b72val Te6edf3targetIndex Tff7b72=
Te6edf3stateTb4b4b4.Te6edf3messagesTb4b4b4.Te6edf3itemSnapshotListTb4b4b4.Te6edf3indexOfFirst Tb4b4b4{ Tffa657itTff7b72?.Te6edf3packetId Tff7b72=Tff7b72= Te6edf3messageTb4b4b4.Te6edf3replyId Tb4b4b4}Tb4b4b4.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72!Tff7b72= Tff7b72-T79c0ff1 Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3targetIndex Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3listStateTb4b4b4.Te6edf3animateScrollToItemTb4b4b4(Te6edf3index Tff7b72= Te6edf3targetIndexTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3hasSamePrev Tff7b72= Te6edf3hasSamePrevTb4b4b4,
Te6edf3hasSameNext Tff7b72= Te6edf3hasSameNextTb4b4b4,
Te6edf3quickEmojis Tff7b72= Te6edf3quickEmojisTb4b4b4,
Te6edf3searchQuery Tff7b72= Te6edf3stateTb4b4b4.Te6edf3searchQueryTb4b4b4,
Te6edf3translationAvailable Tff7b72= Te6edf3stateTb4b4b4.Te6edf3translationAvailableTb4b4b4,
Te6edf3isDirectMessage Tff7b72= Te6edf3isDirectMessageConversationTb4b4b4,
Te6edf3onTranslate Tff7b72= Tb4b4b4{ Te6edf3handlersTb4b4b4.Te6edf3onTranslateTb4b4b4(Te6edf3messageTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3onToggleTranslation Tff7b72= Tb4b4b4{ Te6edf3handlersTb4b4b4.Te6edf3onToggleTranslationTb4b4b4(Te6edf3messageTb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4)
Tf0883e@Composable
Tff7b72private Tff7b72fun Td2a8ffAutoScrollToBottomPagedTb4b4b4(
Te6edf3listStateTb4b4b4: Te6edf3LazyListStateTb4b4b4,
Te6edf3messagesTb4b4b4: Te6edf3LazyPagingItemsTff7b72<Te6edf3MessageTff7b72>Tb4b4b4,
Te6edf3hasUnreadMessagesTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hasDialogOpenTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4,
Te6edf3itemThresholdTb4b4b4: Tffa657Int Tff7b72= T79c0ff3Tb4b4b4,
Tb4b4b4) Tff7b72= Te6edf3withTb4b4b4(Te6edf3listStateTb4b4b4) Tb4b4b4{
T8b949e// Cache whether we were at the bottom - only update when not actively scrolling
T8b949e// This prevents stuttering while still tracking position for auto-scroll
Tff7b72var Te6edf3cachedAtBottom Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3mutableStateOfTb4b4b4(Tff7b72trueTb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3isCurrentlyAtBottom Tff7b72by
Te6edf3rememberTb4b4b4(Te6edf3hasUnreadMessagesTb4b4b4, Te6edf3hasDialogOpenTb4b4b4) Tb4b4b4{
Te6edf3derivedStateOf Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3hasDialogOpenTb4b4b4) Tb4b4b4{
Tff7b72false
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3isAtBottom Tff7b72=
Te6edf3firstVisibleItemIndex Tff7b72=Tff7b72= T79c0ff0 Tff7b72&Tff7b72&
Te6edf3firstVisibleItemScrollOffset Tff7b72<Tff7b72= Te6edf3UnreadUiDefaultsTb4b4b4.Te6edf3AUTO_SCROLL_BOTTOM_OFFSET_TOLERANCE
Tff7b72val Te6edf3isNearBottom Tff7b72= Te6edf3firstVisibleItemIndex Tff7b72<Tff7b72= Te6edf3itemThreshold
Te6edf3isAtBottom Tff7b72|Tff7b72| Tb4b4b4(Tff7b72!Te6edf3hasUnreadMessages Tff7b72&Tff7b72& Te6edf3isNearBottomTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// Update cached position only when scroll is idle to prevent stuttering
Te6edf3LaunchedEffectTb4b4b4(Te6edf3isScrollInProgressTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3isScrollInProgressTb4b4b4) Tb4b4b4{
Te6edf3cachedAtBottom Tff7b72= Te6edf3isCurrentlyAtBottom
Tb4b4b4}
Tb4b4b4}
T8b949e// Consolidated scroll logic to prevent race conditions
T8b949e// Fixes issue where multiple scroll operations could trigger simultaneously
T8b949e// by unifying all scroll triggers into a single LaunchedEffect
Te6edf3LaunchedEffectTb4b4b4(Te6edf3messagesTb4b4b4.Te6edf3itemCountTb4b4b4) Tb4b4b4{
T8b949e// Use cached position (captured when scroll was idle) to decide if we should auto-scroll
T8b949e// This prevents race conditions where new message renders before we check position
Tff7b72if Tb4b4b4(Te6edf3cachedAtBottom Tff7b72&Tff7b72& Te6edf3messagesTb4b4b4.Te6edf3itemCount Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3scrollToItemTb4b4b4(T79c0ff0Tb4b4b4)
T8b949e// Update cache immediately after scrolling
Te6edf3cachedAtBottom Tff7b72= Tff7b72true
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8fffindFirstVisibleUnreadMessageTb4b4b4(Te6edf3messagesTb4b4b4: Te6edf3LazyPagingItemsTff7b72<Te6edf3MessageTff7b72>Tb4b4b4, Te6edf3visibleIndexTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Message? Tb4b4b4{
Tff7b72val Te6edf3snapshot Tff7b72= Te6edf3messagesTb4b4b4.Te6edf3itemSnapshotList
Tff7b72if Tb4b4b4(Te6edf3visibleIndex Tff7b72>Tff7b72= Te6edf3snapshotTb4b4b4.Te6edf3sizeTb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3firstVisibleUnreadIndex Tff7b72=
Tb4b4b4(Te6edf3visibleIndex Te6edf3until Te6edf3snapshotTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4.Te6edf3firstOrNull Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Tff7b72val Te6edf3msg Tff7b72= Te6edf3snapshotTff7b72[Te6edf3iTff7b72]
Te6edf3msg Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Tff7b72!Te6edf3msgTb4b4b4.Te6edf3read Tff7b72&Tff7b72& Tff7b72!Te6edf3msgTb4b4b4.Te6edf3fromLocal
Tb4b4b4}
Tff7b72return Te6edf3firstVisibleUnreadIndexTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3snapshotTff7b72[Tffa657itTff7b72] Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8fffindLastUnreadMessageIndexTb4b4b4(Te6edf3messagesTb4b4b4: Te6edf3LazyPagingItemsTff7b72<Te6edf3MessageTff7b72>Tb4b4b4)Tb4b4b4: Tffa657Int? Tb4b4b4{
Tff7b72val Te6edf3snapshot Tff7b72= Te6edf3messagesTb4b4b4.Te6edf3itemSnapshotList
Tff7b72return Tb4b4b4(T79c0ff0 Te6edf3until Te6edf3snapshotTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4.Te6edf3lastOrNull Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Tff7b72val Te6edf3msg Tff7b72= Te6edf3snapshotTff7b72[Te6edf3iTff7b72]
Te6edf3msg Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Tff7b72!Te6edf3msgTb4b4b4.Te6edf3read Tff7b72&Tff7b72& Tff7b72!Te6edf3msgTb4b4b4.Te6edf3fromLocal
Tb4b4b4}
Tb4b4b4}
Tf0883e@OptInTb4b4b4(Te6edf3FlowPreviewTff7b72::Te6edf3classTb4b4b4)
Tf0883e@Composable
Tff7b72private Tff7b72fun Td2a8ffUpdateUnreadCountPagedTb4b4b4(
Te6edf3listStateTb4b4b4: Te6edf3LazyListStateTb4b4b4,
Te6edf3messagesTb4b4b4: Te6edf3LazyPagingItemsTff7b72<Te6edf3MessageTff7b72>Tb4b4b4,
Te6edf3onUnreadChangeTb4b4b4: Tb4b4b4(Tffa657LongTb4b4b4, Tffa657LongTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3currentOnUnreadChange Tff7b72by Te6edf3rememberUpdatedStateTb4b4b4(Te6edf3onUnreadChangeTb4b4b4)
Tff7b72var Te6edf3isResumed Tff7b72by Te6edf3remember Tb4b4b4{ Te6edf3mutableStateOfTb4b4b4(Tff7b72falseTb4b4b4) Tb4b4b4}
T8b949e// Track lifecycle state changes
Te6edf3LifecycleResumeEffectTb4b4b4(Tffa657UnitTb4b4b4) Tb4b4b4{
Te6edf3isResumed Tff7b72= Tff7b72true
Te6edf3onPauseOrDispose Tb4b4b4{ Te6edf3isResumed Tff7b72= Tff7b72false Tb4b4b4}
Tb4b4b4}
T8b949e// Track remote message count to restart effect when remote messages change
T8b949e// This fixes race condition when sending/receiving messages during debounce period
T8b949e// Optimized: Use itemSnapshotList instead of iterating through indices
Tff7b72val Te6edf3remoteMessageCount Tff7b72by
Te6edf3rememberTb4b4b4(Te6edf3messagesTb4b4b4.Te6edf3itemCountTb4b4b4) Tb4b4b4{ Te6edf3derivedStateOf Tb4b4b4{ Te6edf3messagesTb4b4b4.Te6edf3itemSnapshotListTb4b4b4.Te6edf3itemsTb4b4b4.Te6edf3count Tb4b4b4{ Tff7b72!Tffa657itTb4b4b4.Te6edf3fromLocal Tb4b4b4} Tb4b4b4} Tb4b4b4}
T8b949e// Mark messages as read after debounce period
T8b949e// Handles both scrolling cases and when all unread messages are visible without scrolling
T8b949e// Effect restarts when isResumed changes, so returning from background will restart the debounce
Te6edf3LaunchedEffectTb4b4b4(Te6edf3remoteMessageCountTb4b4b4, Te6edf3listStateTb4b4b4, Te6edf3isResumedTb4b4b4) Tb4b4b4{
Te6edf3snapshotFlow Tb4b4b4{
T8b949e// Emit when scroll stops OR when at initial position (covers no-scroll case)
T8b949e// Include isResumed in the snapshot so lifecycle changes trigger new emissions
Tff7b72if Tb4b4b4(Te6edf3listStateTb4b4b4.Te6edf3isScrollInProgress Tff7b72|Tff7b72| Tff7b72!Te6edf3isResumedTb4b4b4) Tb4b4b4{
Tff7b72null T8b949e// Scrolling in progress or not resumed, don't emit
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3listStateTb4b4b4.Te6edf3firstVisibleItemIndex T8b949e// Emit current position when not scrolling and resumed
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3debounceTb4b4b4(Te6edf3timeoutMillis Tff7b72= Te6edf3UnreadUiDefaultsTb4b4b4.Te6edf3SCROLL_DEBOUNCE_MILLISTb4b4b4)
Tb4b4b4.Te6edf3collectLatest Tb4b4b4{ Te6edf3index Tff7b72-Tff7b72>
T8b949e// Only mark messages as read if we have a valid index (screen is visible and not scrolling)
Tff7b72if Tb4b4b4(Te6edf3index Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3lastUnreadIndex Tff7b72= Te6edf3findLastUnreadMessageIndexTb4b4b4(Te6edf3messagesTb4b4b4)
T8b949e// If we're at/past the oldest unread, mark the first visible unread message
T8b949e// Since newer messages have HIGHER timestamps, marking a newer message's timestamp
T8b949e// will batch-mark all older messages via SQL: WHERE received_time <= timestamp
Tff7b72if Tb4b4b4(Te6edf3lastUnreadIndex Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3index Tff7b72<Tff7b72= Te6edf3lastUnreadIndexTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3firstVisibleUnread Tff7b72= Te6edf3findFirstVisibleUnreadMessageTb4b4b4(Te6edf3messagesTb4b4b4, Te6edf3indexTb4b4b4)
Te6edf3firstVisibleUnreadTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3currentOnUnreadChangeTb4b4b4(Tffa657itTb4b4b4.Te6edf3uuidTb4b4b4, Tffa657itTb4b4b4.Te6edf3receivedTimeTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.12s